#include "Graphics.h"
#include "Shape.h"

namespace Cartesia {


LPDIRECT3D9				Graphics::_d3d		= NULL;
LPDIRECT3DDEVICE9		Graphics::_device	= NULL;
D3DDISPLAYMODE			Graphics::_displayMode;
D3DFORMAT				Graphics::_format;
D3DPRESENT_PARAMETERS	Graphics::_params;
D3DCAPS9				Graphics::_caps;

D3DXMATRIX				Graphics::_theMatrix;

vector<IDirect3DTexture9*>	Graphics::_textures;

VertexBuffer<VertexColor, FVF_VERTEX_COLOR>		Graphics::_VBColor;
VertexBuffer<VertexTexture, FVF_VERTEX_TEXTURE>	Graphics::_VBTexture;

int		Graphics::_iCurrentTextureId = -1;
int		Graphics::_width		= 640;
int		Graphics::_height		= 480;
DWORD	Graphics::_clearColor	= 0xffcccccc;
bool	Graphics::_fullscreen	= false;

//-------------------------------------------------------------------

Graphics::Graphics(void)
{

}

Graphics::~Graphics(void)
{
}

//-------------------------------------------------------------------

void Graphics::BindTexture(int iTextureId)
{
	//if (_iCurrentTextureId != iTextureId)
	{
		_iCurrentTextureId = iTextureId;

		if (_iCurrentTextureId != -1)
			_device->SetTexture(0, _textures[_iCurrentTextureId]);
		else
			_device->SetTexture(0, NULL);
	}
}


//-------------------------------------------------------------------

bool Graphics::LoadTexture(TextureInfo * pTextureInfo)
{
	LPDIRECT3DTEXTURE9	pBitmapTexture;
	D3DXIMAGE_INFO		ImageInfo;

	// Zak usa InMemory pero antes carga del archivo a binario en memoria
	// y despues lee desde la memoria
	HRESULT hr = D3DXCreateTextureFromFileEx(
		_device, 
		pTextureInfo->szFileName, 
		//pTextureInfo->size, 
		pTextureInfo->width, 
		pTextureInfo->height, 
		pTextureInfo->mipLevels, 
		0, 
		D3DFMT_UNKNOWN, 
		D3DPOOL_MANAGED, 
		D3DX_FILTER_NONE, 
		D3DX_FILTER_NONE, 
		pTextureInfo->colorKey, 
		&ImageInfo, 
		NULL,
		&pBitmapTexture
		);

	if (hr == D3D_OK)
	{
		_iCurrentTextureId++; //Start 0
		pTextureInfo->width		= ImageInfo.Width;
		pTextureInfo->height	= ImageInfo.Height;
		pTextureInfo->mipLevels	= ImageInfo.MipLevels;
		_textures.push_back(pBitmapTexture);
		return true;
	}
	else
		return false;
}

//-------------------------------------------------------------------

int Graphics::Shutdown()
{ 
	if (_device)
		_device->Release();

	return 0; 
}

//-------------------------------------------------------------------

int Graphics::Startup(HINSTANCE hInstance, int width, int height)
{
	_width		= width;
	_height		= height;
	_fullscreen	= false;

	// Creo un objeto D3D (NO es el Device)
	_d3d = Direct3DCreate9(D3D_SDK_VERSION);

	if (!_d3d)
		return -1;

	// Obtiene las propiedades actuales del dispositivo de video
	HRESULT hr = _d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &_displayMode);

	// Formato del color
	_format = D3DFMT_A8R8G8B8;

	// Limpio la memoria donde se va a alocar los parametros de DX
	ZeroMemory(&_params,sizeof(_params));

	// Parametros de DirectX
	_params.BackBufferWidth		=	_width;
	_params.BackBufferHeight	=	_height;
	_params.BackBufferFormat	=	_format;
	_params.BackBufferCount		=	1;
	_params.hDeviceWindow		=	0; // Ventana activa

	_params.SwapEffect	=	D3DSWAPEFFECT_FLIP;
	_params.Flags		=	D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	_params.Windowed	=	!_fullscreen;

	_params.PresentationInterval	= D3DPRESENT_INTERVAL_IMMEDIATE;
	_params.EnableAutoDepthStencil	= TRUE;
	_params.AutoDepthStencilFormat	= D3DFMT_D16;

	_params.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;


	HWND windowHandle = Window::Create("Cartesia Engine", _width, _height, hInstance);

	if (windowHandle != NULL)
	{
		// Crea el dispositivo por Hardware
		hr = _d3d->CreateDevice(	D3DADAPTER_DEFAULT,
									D3DDEVTYPE_HAL,
									windowHandle,
									D3DCREATE_HARDWARE_VERTEXPROCESSING,
									&_params,
									&_device
								);


		// Crea el dispositivo por Software
		if (hr != D3D_OK)
		{
			hr = _d3d->CreateDevice(	D3DADAPTER_DEFAULT,
										D3DDEVTYPE_HAL,
										windowHandle,
										D3DCREATE_SOFTWARE_VERTEXPROCESSING,
										&_params,
										&_device
									);
		}
	}

	// Si falla la creacion del dispositivo
	if (hr != D3D_OK)
	{
		_d3d->Release();
		return -1;
	}

	// RenderStates
	_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
	_device->SetRenderState(D3DRS_LIGHTING, FALSE);
	_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
	_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);

	// Matrices de vista y proyeccion
	D3DXMATRIX view;
	D3DXMATRIX proj;

	// Vectores que conforman la direccion de la "camara"
	D3DXVECTOR3 eye(0,0,-10);
	D3DXVECTOR3 at(0,0,1);
	D3DXVECTOR3 up(0,1,0);

	// Seteo hacia donde ve la camara y la vista ortogonal
	D3DXMatrixLookAtLH(&view,&eye,&at,&up);
	D3DXMatrixOrthoLH(&proj,_width,_height,-25,25);

	_device->SetTransform(D3DTS_VIEW, &view);
	_device->SetTransform(D3DTS_PROJECTION, &proj);

	// Creo un VertexBufferColor
	_VBColor.Create(_device, true);
	_VBTexture.Create(_device, true);

	return 0;
}

//-------------------------------------------------------------------

void Graphics::SetColor(DWORD color)
{
	_clearColor = color;
}

//-------------------------------------------------------------------

void Graphics::SetResolution(int width, int height)
{
	if (width > 0 && height > 0)
	{
		_width  = width;
		_height = height;
	}	
}

//-------------------------------------------------------------------

void Graphics::Clear()
{
	_device->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, _clearColor, 1.0f, 0);
}

//-------------------------------------------------------------------

void Graphics::BeginScene()
{
	_device->BeginScene();
}

//-------------------------------------------------------------------

void Graphics::EndScene()
{
	_device->EndScene();
}

//-------------------------------------------------------------------

void Graphics::Present()
{
	_device->Present(NULL,NULL,NULL,NULL);
}


//-------------------------------------------------------------------

void Graphics::Draw(VertexColor * pVertexCollection, D3DPRIMITIVETYPE ePrimitive, uint vertices)
{
	_VBColor.Bind();
	_VBColor.Draw(pVertexCollection, ePrimitive, vertices);
}

//-------------------------------------------------------------------

void Graphics::Draw(VertexTexture * pVertexCollection, D3DPRIMITIVETYPE ePrimitive, uint vertices)
{
	_VBTexture.Bind();
	_VBTexture.Draw(pVertexCollection, ePrimitive, vertices);
}

//-------------------------------------------------------------------

// LOAD IDENTITY
// Carga la Matriz Identidad
void Graphics::LoadIdentity()
{
	D3DXMatrixIdentity(&_theMatrix);
	_device->SetTransform(D3DTS_WORLD, &_theMatrix);
}


//-------------------------------------------------------------------

void Graphics::RotateX(float angle)
{
	D3DXMatrixRotationX(&_theMatrix, angle);
	_device->MultiplyTransform(D3DTS_WORLD, &_theMatrix);
}


//-------------------------------------------------------------------

void Graphics::RotateY(float angle)
{
	D3DXMatrixRotationY(&_theMatrix, angle);
	_device->MultiplyTransform(D3DTS_WORLD, &_theMatrix);
}

//-------------------------------------------------------------------

void Graphics::RotateZ(float angle)
{
	D3DXMatrixRotationZ(&_theMatrix, angle);
	_device->MultiplyTransform(D3DTS_WORLD, &_theMatrix);
}

//-------------------------------------------------------------------

void Graphics::Translate(float x, float y, float z)
{
	D3DXMatrixTranslation(&_theMatrix, x, y, z);
	_device->MultiplyTransform(D3DTS_WORLD, &_theMatrix);
}


//-------------------------------------------------------------------

ID3DXMesh * Graphics::CreateBox(float width, float height, float depth)
{
	ID3DXMesh * box;
	D3DXCreateBox(_device, width, height, depth, &box, 0);
	return box;
}

//-------------------------------------------------------------------

ID3DXMesh * Graphics::CreateCylinder(float radius1, float radius2, float length, uint slices, int stacks)
{
	ID3DXMesh * cylinder;
	D3DXCreateCylinder(_device, radius1, radius2, length, slices, stacks, &cylinder, 0);
	return cylinder;
}
//-------------------------------------------------------------------

ID3DXMesh * Graphics::CreatePolygon(float length, uint sides)
{
	ID3DXMesh * poly;
	D3DXCreatePolygon(_device, length, sides, &poly, 0);
	return poly;
}

//-------------------------------------------------------------------

ID3DXMesh * Graphics::CreateSphere(float radius, uint slices, uint stacks)
{
	ID3DXMesh * sphere;
	D3DXCreateSphere(_device, radius, slices, stacks, &sphere, 0);
	return sphere;
}

//-------------------------------------------------------------------

ID3DXMesh * Graphics::CreateTeapot()
{
	ID3DXMesh * teapot;
	D3DXCreateTeapot(_device, &teapot, 0);
	return teapot;
}

//-------------------------------------------------------------------

ID3DXMesh * Graphics::CreateTorus(float innerRadius, float outerRadius, uint sides, uint rings)
{
	ID3DXMesh * torus;
	D3DXCreateTorus(_device, innerRadius, outerRadius, sides, rings, &torus, 0);
	return torus;
}

//-------------------------------------------------------------------

void Graphics::DrawD3DXMesh(ID3DXMesh * pMesh)
{
	//_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
	pMesh->DrawSubset(0);
}

//-------------------------------------------------------------------


void Graphics::SetProjectionPerspective()
{
	// Matrices de vista y proyeccion
	D3DXMATRIX view;
	D3DXMATRIX proj;

	// Vectores que conforman la direccion de la "camara"
	/*
	// Desde arriba
	D3DXVECTOR3 eye(0,5000,0);
	D3DXVECTOR3 at(0,0,1);
	D3DXVECTOR3 up(0,-1,0);
	*/
	// Desde el frente
	D3DXVECTOR3 eye(0,0,-5000);
	D3DXVECTOR3 at(0,0,1);
	D3DXVECTOR3 up(0,1,0);

	// Seteo hacia donde ve la camara y la vista ortogonal
	D3DXMatrixLookAtLH(&view,&eye,&at,&up);
	D3DXMatrixPerspectiveFovLH(&proj, 45 * PI_OVER_180, 1.33f, -3000, 3000);

	_device->SetTransform(D3DTS_VIEW, &view);
	_device->SetTransform(D3DTS_PROJECTION, &proj);
}

//-------------------------------------------------------------------

void Graphics::SetRenderState(D3DRENDERSTATETYPE State, DWORD Value)
{
	_device->SetRenderState(State, Value);
}

//-------------------------------------------------------------------


} // End Namespace Cartesia